This is content in the body of our modal
revision:
A Web Component is a way to create an encapsulated, single-responsibility code block, which can be reused on any page. It is a suite of different technologies allowing you to create reusable custom elements, with their functionality encapsulated away from the rest of your code, and utilize them in your web apps.
Using a web component is much like using any other existing HTML element: they can be configured using attributes, queried for using JavaScript, and even styled through CSS. As long as the browser knows they exist, they are treated no differently.
Web Components consists of three main technologies. Each of the technologies can be used independently or combined with any of the others.
Custom elements: a set of JavaScript APIs that allow you to define custom elements and their behavior.
Custom elements are HTML elements (like <div>, <section>or <article>). We can name them ourselves and they are defined via a browser API.
Custom elements are just like standard HTML elements (i.e. names in angle brackets), except that they always have a dash in them, like <news-slider> or <bacon-cheeseburger>. Browser vendors have committed not to create new built-in elements containing a dash in their names to prevent conflicts.
Custom elements contain their own semantics, behaviors, markup and can be shared across frameworks and browsers.
Shadow DOM: a set of JavaScript APIs for attaching an encapsulated "shadow" DOM tree to an element — which is rendered separately from the main document DOM — and controlling associated functionality.
In this way, you can keep an element's features private, so they can be scripted and styled without the fear of collision with other parts of the document.
The shadow DOM is an encapsulated version of the DOM. This allows authors to effectively isolate DOM fragments from one another, including anything that could be used as a CSS selector and the styles associated with them.
Generally, any content inside of the document's scope is referred to as the light DOM, and anything inside a shadow root is referred to as the shadow DOM.
The shadow DOM works sort of like an <iframe>where the content is cut off from the rest of the document; however, when we create a shadow root, we still have total control over that part of our page, but scoped to a context. This is what we call encapsulation.
HTML templates: the ><template> element and <slot> element enable you to write markup templates that are not displayed in the rendered page.
These can then be reused multiple times as the basis of a custom element's structure. The HTML <template> element allows us to stamp out re-usable templates of code inside a normal HTML flow that won't be immediately rendered, but can be used at a later time.
Custom elements are just like the standard HTML elements — names in angle brackets — except they always have a dash in them, like <news-slider> or <bacon-cheeseburger>.
Custom elements contain their own semantics, behaviors, markup and can be shared across frameworks and browsers.
autonomous custom elements : “all-new” elements, extending the abstract HTMLElement class.
customized built-in elements : extending built-in elements, like a customized button, based on HTMLButtonElement etc.
examples
<div>
<my-component></my-component>
</div>
<script>
class MyComponent extends HTMLElement {
// connect component
connectedCallback() {
this.innerHTML = `<h1>Hello world</h1>`;
}
}
// register component
customElements.define('my-component', MyComponent);
</script>
explanation:
In this example, we define <my-component>, our very own HTML element. It doesn't do much, however this is the basic building block of a custom element.
All custom elements must in some way extend an HTMLElement in order to be registered with the browser.
To do anything useful, the class requires a method named connectedCallback() which is invoked when the element is added to a document.
The class must be registered with the CustomElementRegistry to define it as a handler for a specific element.
The browser now associates the <hello-world> element with the MyComponent class when your JavaScript is loaded (e.g. <script type="module" src="./mycomponent.js"></script>).
Custom elements exist without third-party frameworks and the browser vendors are dedicated to the continued backward compatibility of the spec, all but guaranteeing that components written according to the specifications will not suffer from breaking API changes.
They extend an existing HTMLElement class, which provides the groundwork for how an element should behave.
Inside, there are a few methods called "reactions" that are called in response to something about that element changing. For example, "connectedCallback" will be called when the new element appears on screen.
These work similarly to the lifecycle methods found in most JavaScript frameworks. Updating the attributes on an element can change how it behaves. When an update happens, the "attributeChangedCallback" reaction will fire, which drives the change. This will only happen for an attribute that is defined inside the observedAttributes array.
An element needs to be defined before the browser can do anything with it. The "define method" here takes two arguments – the tag name, and the class it should use. All tag names must contain a "-"" character to avoid any clashes with any future native elements.
Once a browser has an element defined, it then finds any of these matching tags and links up their behaviour to the class in a process known as 'upgrading'.
There are two types of custom element – 'autonomous' or 'customised built-in'.
Autonomous custom elements are not related to any existing element. Much like a <div>or <span>they do not provide any meaning to their content.
A customised built-in element – as the name implies – can enhance an existing element with new functionality. They maintain that element's normal semantic behaviours, while also being open to change. If an <input>element was customised, for example, it would still be picked up and submitted as part of a form.
The definition also needs to define the tag of that element through its third argument. They are also used slightly differently.
Instead of a new tag, they extend the existing tag by using the "is" attribute. The browser can read this, and upgrade them in the same way as it can an autonomous component.
CustomElementRegistry: contains functionality related to custom elements, most notably the CustomElementRegistry.define() method used to register new custom elements so they can then be used in your document.
Window.customElements: returns a reference to the CustomElementRegistry object.
Life cycle callbacks: special callback functions defined inside the custom element's class definition, which affect its behavior:
connectedCallback: invoked when the custom element is first connected to the document's DOM.
disconnectedCallback: invoked when the custom element is disconnected from the document's DOM.
adoptedCallback: invoked when the custom element is moved to a new document.
attributeChangedCallback: invoked when one of the custom element's attributes is added, removed, or changed.
Extensions for creating custom built-in elements: The "is" global HTML attribute allows you to specify that a standard HTML element should behave like a registered custom built-in element. The "is" option of the Document.createElement() method allows you to create an instance of a standard HTML element that behaves like a given registered custom built-in element.
CSS pseudo-classes: pseudo-classes relating specifically to custom elements:
:defined: matches any element that is defined, including built in elements and custom elements defined with CustomElementRegistry.define().
:host: selects the shadow host of the shadow DOM containing the CSS it is used inside.
:host(): selects the shadow host of the shadow DOM containing the CSS it is used inside (so you can select a custom element from inside its shadow DOM) — but only if the selector given as the function's parameter matches the shadow host.
:host-context(): selects the shadow host of the shadow DOM containing the CSS it is used inside (so you can select a custom element from inside its shadow DOM) — but only if the selector given as the function's parameter matches the shadow host's ancestor(s) in the place it sits inside the DOM hierarchy.
CSS pseudo-elements: pseudo-elements relating specifically to custom elements:
::part: represents any element within a shadow tree that has a matching part attribute.
Selectors and styles inside of a shadow DOM node don't leak outside of the shadow root and styles from outside the shadow root don't leak in. There are a few exceptions that inherit from the parent document, like font family and document font sizes (e.g. rem) that can be overridden internally.
All shadow roots still exist in the same document so that all code can be written inside a given context but no worry about conflicts with other styles or selectors.
This allows authors to effectively isolate DOM fragments from one another, including anything that could be used as a CSS selector and the styles associated with them.
Any content inside of the document's scope is referred to as the "light DOM", and anything inside a shadow root is referred to as the "shadow DOM".
When using the light DOM, an element can be selected by using document.querySelector('selector') or by targeting any element's children by using element.querySelector('selector').
In the same way, a shadow root's children can be targeted by calling shadowRoot.querySelector where shadowRoot is a reference to the document fragment — the difference being that the shadow root's children will not be select-able from the light DOM.
For example, If we have a shadow root with a <button> inside of it, calling shadowRoot.querySelector('button') would return our button, but no invocation of the document's query selector will return that element because it belongs to a different DocumentOrShadowRoot instance. Style selectors work in the same way.
In this respect, the shadow DOM works sort of like an <iframe> where the content is cut off from the rest of the document; however, when we create a shadow root, we still have total control over that part of our page, but scoped to a context. This is what we call encapsulation.
Any content can be added to the shadow DOM just like the regular – or 'light' – DOM, but it has no effect on what's happening outside of it.
Likewise, nothing in the light DOM can access the shadow DOM directly. This means we can add classes, styles and scripts anywhere in the shadow DOM without worrying about clashes.
The best use of the shadow DOM with web components comes when coupled with a custom element. By having a shadow DOM in charge of the content, any time this component is reused, its styles and structure will not affect the rest of the page.
ShadowRoot: represents the root node of a shadow DOM subtree.
DocumentOrShadowRoot: a mixin defining features that are available across document and shadow roots.
Element extensions: extensions to the Element interface related to shadow DOM: the Element.attachShadow() method attaches a shadow DOM tree to the specified element; the Element.shadowRoot property returns the shadow root attached to the specified element, or null if there is no shadow root attached.
Relevant Node additions: additions to the node interface relevant to shadow DOM: the Node.getRootNode() method returns the context object's root, which optionally includes the shadow root if it is available; the Node.isConnected property returns a boolean indicating whether or not the node is connected (directly or indirectly) to the context object, e.g. the Document object in the case of the normal DOM, or the ShadowRoot in the case of a shadow DOM
Event extensions: Extensions to the event interface related to shadow DOM: Event.composed returns a Boolean which indicates whether the event will propagate across the shadow DOM boundary into the standard DOM (true), or not (false); Event.composedPath returns the event’s path (objects on which listeners will be invoked). This does not include nodes in shadow trees if the shadow root was created with ShadowRoot.mode closed.
examples
<div>
<div id="example">This will use the CSS background</div>
<button id="button">Not tomato</button>
</div>
<script>
const shadowRoot = document.getElementById('example').attachShadow({ mode: 'open' });
shadowRoot.innerHTML = `<style>
button {
background: tomato;
color: white;
}
</style>
<button id="button"><slot></slot> tomato</button>`;
</script>
A shadow root can also include content from its containing document by using the <slot> element. Using a slot will drop user content from the outer document at a designated spot in your shadow root.
const shadow = this.attachShadow({ mode: 'closed' });
The mode can be:
"open" : JavaScript in the outer page can access the shadow DOM (using Element.shadowRoot);
"closed" : the shadow DOM can only be accessed within the Web Component.
The shadow DOM can be manipulated like any other DOM element:
connectedCallback() {
const shadow = this.attachShadow({ mode: 'closed' });
shadow.innerHTML = `
<style>
p {
text-align: center;
font-weight: normal;
padding: 1em;
margin: 0 0 2em 0;
background-color: #eee;
border: 1px solid #666;
}
</style>
<p>Hello ${ this.name }!</p>`;
}
explanation:
The component renders the “Hello” text inside a <p> element and styles it. It cannot be modified by JavaScript or CSS outside the component, although some styles such as the font and color are inherited from the page because they were not explicitly defined. The styles scoped to this Web Component cannot affect other paragraphs on the page or even other <hello-world> components.
Note that the CSS :host selector can style the outer <hello-world> element from within the Web Component.
On its own, it has no appearance and remains inert, meaning nothing inside is parsed or executed until told to, including requests for external media such as images or video. JavaScript cannot query the contents either, as browsers will only see it as an empty element.
A regular query will pick up the <template>element itself. The importNode method creates a copy of its contents, with the second argument telling it to take a deep copy of everything. Finally, it can be added to the document like any other element.
Templates can contain anything an HTML page can, including CSS and JavaScript. As soon as the element is applied to the page, those styles apply and the scripts execute.
Bear in mind that these will run globally and so can override styles and values if care isn't taken. The best part about templates is that they are not just limited to web components.
The examples here apply to any web page, but become particularly powerful when paired with web components, in particular the shadow DOM.
<template >: contains an HTML fragment that is not rendered when a containing document is initially loaded, but can be displayed at runtime using JavaScript, mainly used as the basis of custom element structures. The associated DOM interface is HTMLTemplateElement.
<slot >: a placeholder inside a web component that you can fill with your own markup, which lets you create separate DOM trees and present them together. The associated DOM interface is HTMLSlotElement.
The slot global HTML attribute: assigns a slot in a shadow DOM shadow tree to an element.
Slotable: a mixin implemented by both Element and Text nodes, defining features that allow them to become the contents of an <slot>element. The mixin defines one attribute, "Slotable.assignedSlot", which returns a reference to the slot the node is inserted in.
Element extensions: extensions to the Element interface related to slots: Element.slot returns the name of the shadow DOM slot attached to the element.
CSS pseudo-elements:Pseudo-elements relating specifically to slots: ::slotted matches any content that is inserted into a slot.
The slotchange event: fired on an HTMLSlotElement instance (<slot>element) when the node(s) contained in that slot changes.
A built-in <template> element serves as a storage for HTML markup templates. The browser ignores its contents, only checks for syntax validity, but we can access and use it in JavaScript, to create other elements.
Its content can be any valid HTML, even if it normally requires a proper enclosing tag. We can put styles and scripts into <template> as well.
The content becomes live (styles apply, scripts run etc) when we insert it into the document.
examples
<div>
<template class="spec-1" id="book-template">
<li><span class="title"></span> — <span class="author"></span></li>
</template>
<template class="spec-1"id="book-template-2">
<li><span class="author"></span>'s classic novel <span class="title"></span></li>
</template>
<ul class="spec-1" id="books"></ul>
<fieldset class="spec-1" id="templates">
<legend>Choose template</legend>
<label>
<input type="radio" name="template" value="book-template" checked> Template One
</label>
<label>
<input type="radio" name="template" value="book-template-2"> Template Two
</label>
</fieldset>
</div>
<style>
label {display: block; margin-bottom: 0.5vw;}
</style>
<script>
'use strict';
const books = [
{ title: 'The Great Gatsby', author: 'F. Scott Fitzgerald' },
{ title: 'A Farewell to Arms', author: 'Ernest Hemingway' },
{ title: 'Catch 22', author: 'Joseph Heller' }
];
function appendBooks(templateId) {
const booksList = document.getElementById('books');
const fragment = document.getElementById(templateId);
// Clear out the content from the ul
booksList.innerHTML = '';
// Loop over the books and modify the given template
books.forEach(book => {
// Create an instance of the template content
const instance = document.importNode(fragment.content, true);
// Add relevant content to the template
instance.querySelector('.title').innerHTML = book.title;
instance.querySelector('.author').innerHTML = book.author;
// Append the instance ot the DOM
booksList.appendChild(instance);
});
}
document.getElementById('templates').addEventListener('change', (event) => appendBooks(event.target.value));
appendBooks('book-template');
</script>
explanation:
The example above wouldn't render any content until a script has consumed the template, instantiated the code and told the browser what to do with it.
The template elements are "user-defined templates in HTML that aren't rendered until called upon." In other words, a template is HTML that the browser ignores until told to do otherwise.
These templates then can be passed around and reused in a lot of interesting ways.
As simple as it might sound, a <template> is an HTML element, so the most basic form of a template with content would be:
<template>
<h1>Hello world</h1>
</template>
Running this in a browser would result in an empty screen as the browser doesn't render the template element's contents. This becomes incredibly powerful because it allows us to define content (or a content structure) and save it for later — instead of writing HTML in JavaScript. In order to use the template, we will need JavaScript.
example
<div>
<template class="spec-1" id="template1">
<h4>Hello fabulous blue planet!</h4>
</template>
</div>
<script>
const template = document.querySelector('#template1');
const node = document.importNode(template.content, true);
document.body.appendChild(node);
</script>
The following events happen in a web component’s lifecycle: element is inserted into the DOM; updates when UI event is being triggered; element deleted from the DOM.
A web component has lifecycle hooks, which are callback functions, to capture these lifecycle events and let us handle them accordingly.
They let us handle these events without creating our own system to do so.
Most JavaScript frameworks provide the same functionality, but web components is a standard so we don’t need to load extra code to be able to use them.
The constructor() is called when the web component is created. It’s called when we create the shadow DOM and it’s used for setting up listeners and initialize a component’s state. However, it’s not recommended that we run things like rendering and fetching resources here. The connectedCallback is better for these kinds of tasks. Defining a constructor is optional for ES6 classes, but an empty one will be created when it’s undefined.
When creating the constructor, we’ve to call "super()"" to call the class that the web component class extends. We can have "return" statements in there and we can’t use "document.write()" or "document.open()" in there. Also, we can’t gain attributes or children in the constructor method.
The static get observedAttributes : returns an array of attributes that the browser will monitor for changes.
The connectedCallback() method is called when an element is added to the DOM. We can be sure that the element is available to the DOM when this method is called. This means that we can safely set attributes, fetch resources, run set up code or render templates.
disconnectedCallback() is called when the element is removed from the DOM. Therefore, it’s an ideal place to add cleanup logic and to free up resources. We can also use this callback to: 1/ notify another part of an application that the element is removed from the DOM; 2/ free resources that won’t be garbage collected automatically like unsubscribing from DOM events, stop interval timers, or unregister all registered callbacks. This hook is never called when the user closes the tab and it can be triggered more than once during its lifetime.
attributeChangedCallback(attrName, oldVal, newVal): we can pass attributes with values to a Web Component like any other attribute. In this callback, we can get the value of the attributes as they’re assigned in the code. We can add a "static get observedAttributes()" hook to define what attribute values we observe.
The adoptedCallback() is called when we call "document.adoptNode" with the element passed in. It only occurs when we deal with iframes. The "adoptNode" method is used to transfer a node from one document to another. An iframe has another document, so it’s possible to call this with iframe’s document object
This gives us a path to define custom HTML tags that can be used in any document that contains the defining class.
Essentially, a custom element consists of two pieces: a tag name and a class that extends the built-in HTMLElement class. The most basic version of a custom element would look like this:
class HelloWorld extends HTMLElement {
// connect component
connectedCallback() {
this.textContent = "Hello, World!";
}
}
customElements.define('hello-world', HelloWorld);
explanation:
In the example above, we defined a new standards-compliant HTML element, <hello-world></hello-world>. It doesn't do much… yet. For now, using the <hello-world> tag in any HTML document will create a new element with a textContent reading “Hello, World!”.
Like any other element, we can add HTML attributes:
<hello-world name="Ann"></hello-world>
This could override the text so "Hello Ann!" is displayed. To achieve this, you can add a constructor() function to the HelloWorld class, which is run when each object is created.
It must: 1/call the super() method to initialize the parent HTMLElement, and 2/ make other initializations. In this case, we'll define a name property that is set to a default of “World”:
class HelloWorld extends HTMLElement {
constructor() {
super();
this.name = 'World';
}
// more code...
}
A static observedAttributes() property should return an array of properties to observe:
// component attributes
static get observedAttributes() {
return ['name'];
}
An attributeChangedCallback() method is called when an attribute is defined in the HTML or changed using JavaScript. It's passed the property name, old value, and new value:
// attribute change
attributeChangedCallback(property, oldValue, newValue) {
if (oldValue === newValue) return;
this[ property ] = newValue;
}
Finally, you need to tweak the message in the connectedCallback() method:
// connect component
connectedCallback() {
this.textContent = `Hello ${ this.name }!`;
}
1/ use the "customElements.define()" browser API methd and a JS class that extends the HTMLElement:
ex.
class DropDownd extends HTMLElement{
// define behavior here
}
window.customElements.define('drop-down', DropDown);
You can also use an anonymous class:
ex.
window.customElements.define{'drop-down', class extends HTMLElement{
// define behavior here
}}
2/ once the custom element is defined, it can be used in a web page:
ex.
<drop-down ></drop-down >
3/ properties can be defined on a custom element:
ex.
class DropDown extends HTMLElement{
// set the "fill" property
set fill(option){
this.setAttribute('fill', option);
}
// get the "fill" property
get fill(){
return this.Attribute('fill');
}
}
and the usage in the browser:
<drop-down fill="true"><</drop-down>
4/ you can also define a constructor in the class:
ex.
class DropDown extends HTMLElement{
constructor(){
super();
}
}
The versatility of template: one of the interesting things about templates is that they can contain any HTML. That includes script and style elements. A very simple example would be a template that appends a button that alerts us when it is clicked.
example
<div>
<template class="spec-1" id="template1">
<script>
const button = document.getElementById('click-me');
button.addEventListener('click', event => alert(event));
</script>
<style>
#click-me {all: unset; background: tomato; border: 0; border-radius: 4px; color: white; font-family: Helvetica;
font-size: 1vw; padding: .5vw 1vw; margin-inline: 2vw;}
</style>
<button class="spec-1" id="click-me">Log click event</button>
</template>
<script>
'use strict';
const template1 = document.getElementById('template1');
document.body.appendChild(document.importNode(template1.content, true));
</script>
</div>
This is content in the body of our modal
<div>
<template class="spec-1" id="dialog-template">
<script>
document.getElementById('launch-dialog').addEventListener('click', () => {
const wrapper = document.querySelector('.wrapper');
const closeButton = document.querySelector('button.close');
const wasFocused = document.activeElement;
wrapper.classList.add('open');
closeButton.focus();
closeButton.addEventListener('click', () => {
wrapper.classList.remove('open');
wasFocused.focus();
});
});
</script>
<style>
.wrapper {opacity: 0; transition: visibility 0s, opacity 0.25s ease-in; }
.wrapper:not(.open) {visibility: hidden;}
.wrapper.open {align-items: center; display: flex; justify-content: center; height: 100vh; position: fixed; top: 0;
left: 0; right: 0; bottom: 0; opacity: 1; visibility: visible;}
.overlay {background: rgba(0, 0, 0, 0.8); height: 100%; position: fixed; top: 0; right: 0; bottom: 0; left: 0;
width: 100%; }
.dialog { background: #ffffff; max-width: 600px; padding: 1rem; position: fixed; }
button {all: unset; cursor: pointer; font-size: 1.25rem; position: absolute; top: 1rem; right: 1rem;}
button:focus {border: 2px solid blue; }
</style>
<div class="wrapper">
<div class="overlay"></div>
<div class="dialog" role="dialog" aria-labelledby="title" aria-describedby="content">
<button class="close" aria-label="Close">✖️</button>
<h1 id="title">Hello world</h1>
<div id="content" class="content">
<p>This is content in the body of our modal</p>
</div>
</div>
</div>
</template>
<button class="spec-1" id="launch-dialog">Launch dialog</button>
<style>
#launch-dialog {background: tomato; border-radius: 4px; color: #fff;font-family: Helvetica, Arial, sans-serif;
padding: 0.5rem 1rem; position: static;}
</style>
<script>
const dialogTemplate = document.getElementById('dialog-template');
document.body.appendChild(document.importNode(dialogTemplate.content, true));
</script>
</div>
My first paragraph
<div>
<div id="DIV-A"></div>
<template id="my-paragraph"><p id="P-one">My first paragraph</p></template>
</div>
<style>
p#P-one { color: lightblue; background-color: #666; padding: 5px;}
</style>
<script>
let template_A = document.getElementById('my-paragraph');
let templateContent = template_A.content;
document.getElementById('DIV-A').appendChild(templateContent);
</script>
<div>
<div id="DIV-B"></div>
<template id="paragraph-one">
<p id="P-two">
<slot name="my-text">My default text</slot>
</p>
</template>
</div>
<style>
p#P-two { color: lightgreen; background-color: #666; padding: 5px;}
</style>
<script>
let template_B = document.getElementById('paragraph-one');
let templateContent_B = template_B.content;
document.getElementById("DIV-B").appendChild(templateContent_B);
</script>
<div>
<div id="DIV-C"></div>
<template id="paragraph-two">
<style>
p#P-three { color: orange; background-color: #666; padding: 5px;}
</style>
<p id="P-three"><slot name="text-one">My second default text</slot></p>
</template>
</div>
<script>
let template_C = document.getElementById('paragraph-two');
let templateContent_C = template_C.content;
document.getElementById("DIV-C").appendChild(templateContent_C);
</script>
<div>
<div id=""DIV-D></div>
<my-paragraph id="paragraph-three">
<span id="span_1" slot="text-two">Let's have some different text!</span>
</my-paragraph>
</div>
<style>
#span_1 { color: yellow; background-color: #666; padding: 5px;}
</style>
<script>
customElements.define('paragraph-three',
class extends HTMLElement {
constructor() {
super();
const template_D = document.getElementById('paragraph-three');
const templateContent_D = template_D.content;
this.attachShadow({mode: 'open'}).appendChild(templateContent_D.cloneNode(true));
}
}
);
</script>
<div>
<my-paragraph id="para-four">
<ul id="ul-1" slot="my-text">
<li>Let's have still a somewhat different text!</li>
<li>In a list!</li>
</ul>
</my-paragraph>
</div>
<style>
#ul-1 { color: orange; background-color: #666; padding: 5px;}
</style>
<script>
customElements.define('para-four',
class extends HTMLElement {
constructor() {
super();
const template_E = document.getElementById('para-four');
const templateContent_E = template_E.content;
this.attachShadow({mode: 'open'}).appendChild(templateContent.cloneNode(true));
}
}
);
</script>
<div>
<my-paragraph>
<add-text id="text-1"></add-text>
</my-paragraph>
</div>
<script>
class AddText extends HTMLElement {
constructor() {
super();
var host = this.attachShadow({mode: 'open'});
const div=document.createElement('div');
div.setAttribute('class', 'main');
const p=document.createElement('p');
p.textContent='Web Components are awesome, what do you think?';
const style=document.createElement('style');
style.textContent=' p {color:burlywood; background-color: green; padding:0.5vw;} ';
host.appendChild(style);
host.appendChild(div);
div.appendChild(p);
}
}
customElements.define('add-text', AddText);
var host = document.querySelector(".main");
var root = host.createShadowRoot();
root.innerHTML = '<p> <strong>Web Components</strong> are awesome, what do you think?</p>'
</script>
<div>
<form>
<div>
<label for="cvc">Enter your CVC <popup-info img="../pics/download.png" data-text=
"Your card validation code (CVC) is an extra security feature — it is the last 3 or 4 numbers
on the back of your card."></popup-info></label>
<input type="text" id="cvc">
</div>
</form>
<br><br>
</div>
<script>
// Create a class for the element
class PopUpInfo extends HTMLElement {
constructor() {
// Always call super first in constructor
super();
// Create a shadow root
const shadow = this.attachShadow({mode: 'open'});
// Create spans
const wrapper = document.createElement('span');
wrapper.setAttribute('class', 'wrapper');
const icon = document.createElement('span');
icon.setAttribute('class', 'icon');
icon.setAttribute('tabindex', 0);
const info = document.createElement('span');
info.setAttribute('class', 'info');
// Take attribute content and put it inside the info span
const text = this.getAttribute('data-text');
info.textContent = text;
// Insert icon
let imgUrl;
if(this.hasAttribute('img')) {
imgUrl = this.getAttribute('img');
} else {
imgUrl = 'default.png';
}
const img = document.createElement('img');
img.src = imgUrl;
icon.appendChild(img);
// Create some CSS to apply to the shadow dom
const style = document.createElement('style');
console.log(style.isConnected);
style.textContent = `
.wrapper {position: relative;}
.info {font-size: 0.8rem; width: 200px; display: inline-block; border: 1px solid black;
padding: 10px; background: white; border-radius: 10px; opacity: 0; transition: 0.6s all;
position: absolute; bottom: 20px; left: 10px; z-index: 3; }
img {width: 1.2rem;}
.icon:hover + .info, .icon:focus + .info {opacity: 1;}
`;
// Attach the created elements to the shadow dom
shadow.appendChild(style);
console.log(style.isConnected);
shadow.appendChild(wrapper);
wrapper.appendChild(icon);
wrapper.appendChild(info);
}
}
// Define the new element
customElements.define('popup-info', PopUpInfo);
</script>
<div>
<ul is="expanding-list">
<li>UK
<ul>
<li>Yorkshire
<ul>
<li>Leeds
<ul>
<li>Train station</li>
<li>Town hall</li>
<li>Headrow</li>
</ul>
</li>
<li>Bradford</li>
<li>Hull</li>
</ul>
</li>
</ul>
</li>
<li>USA
<ul>
<li>California
<ul>
<li>Los Angeles</li>
<li>San Francisco</li>
<li>Berkeley</li>
</ul>
</li>
<li>Nevada</li>
<li>Oregon</li>
</ul>
</li>
</ul>
<ul>
<li>Not</li>
<li>an</li>
<li>expanding</li>
<li>list</li>
</ul>
</div>
<script>
// Create a class for the element
class ExpandingList extends HTMLUListElement {
constructor() {
// Always call super first in constructor; return value from super() is a reference to this element
self = super();
// Get ul and li elements that are a child of this custom ul element
// li elements can be containers if they have uls within them
const uls = Array.from(self.querySelectorAll('ul'));
const lis = Array.from(self.querySelectorAll('li'));
// Hide all child uls
// These lists will be shown when the user clicks a higher level container
uls.forEach(ul => {
ul.style.display = 'none';
});
// Look through each li element in the ul
lis.forEach(li => {
// If this li has a ul as a child, decorate it and add a click handler
if (li.querySelectorAll('ul').length > 0) {
// Add an attribute which can be used by the style
// to show an open or closed icon
li.setAttribute('class', 'closed');
// Wrap the li element's text in a new span element
// so we can assign style and event handlers to the span
const childText = li.childNodes[0];
const newSpan = document.createElement('span');
// Copy text from li to span, set cursor style
newSpan.textContent = childText.textContent;
newSpan.style.cursor = 'pointer';
// Add click handler to this span
newSpan.onclick = self.showul;
// Add the span and remove the bare text node from the li
childText.parentNode.insertBefore(newSpan, childText);
childText.parentNode.removeChild(childText);
}
});
}
// li click handler
showul = function (e) {
// next sibling to the span should be the ul
const nextul = e.target.nextElementSibling;
// Toggle visible state and update class attribute on ul
if (nextul.style.display == 'block') {
nextul.style.display = 'none';
nextul.parentNode.setAttribute('class', 'closed');
} else {
nextul.style.display = 'block';
nextul.parentNode.setAttribute('class', 'open');
}
};
}
// Define the new element
customElements.define('expanding-list', ExpandingList, { extends: 'ul' });
</script>
<div>
<custom-element foo="foo" bar="bar" baz="baz"></custom-element>
</div>
<script>
class BlinkElement extends HTMLElement {
constructor() {
super();
}
connectedCallback() {
const shadow = this.attachShadow({mode: 'open'});
this.span = document.createElement('span');
this.span.textContent = this.getAttribute('text');
const style = document.createElement('style');
style.textContent = 'span { color: black; background-color: lightblue; margin-left: 2vw; font-size: 2vw; }';
this.intervalTimer = setInterval(() => {
let styleText = this.style.textContent;
if (style.textContent.includes('red')) {
style.textContent = 'span { color: black }';
} else {
style.textContent = 'span { color: red }';
}
}, 2000)
shadow.appendChild(style);
shadow.appendChild(this.span);
}
disconnectedCallback() {
clearInterval(this.intervalTimer);
}
static get observedAttributes() {
return ['text'];
}
attributeChangedCallback(name, oldValue, newValue) {
if (name === 'text') {
if (this.span) {
this.span.textContent = newValue;
}
}
}
}
const blink2 = document.createElement('blink-element');
document.body.appendChild(blink2);
blink2.setAttribute('text', 'bar');
blink2.setAttribute('text', 'baz');
// const blink2 = document.createElement('blink-element');
// document.body.appendChild(blink2);
// blink2.setAttribute('text', 'bar');
// document.body.removeChild(blink2);
customElements.define('blink-element', BlinkElement);
</script>